IMPORT

Librairies

library(phyloseq) # for phyloseq object
library(Biostrings)
library(ggplot2)
library(cowplot)
library(tidyverse)
library("plotly") # plot 3D
library("microbiome") # for centered log-ratio
library("coda") # Aitchison distance
library("coda.base") # Aitchison distance
library("vegan") # NMDS
library(pheatmap) # for heatmap

Data

prune_samples(sample_sums(physeq.agp)>=500, physeq.agp)
phyloseq-class experiment-level object
otu_table()   OTU Table:         [ 6391 taxa and 1190 samples ]
sample_data() Sample Data:       [ 1190 samples by 25 sample variables ]
tax_table()   Taxonomy Table:    [ 6391 taxa by 7 taxonomic ranks ]
phy_tree()    Phylogenetic Tree: [ 6391 tips and 6389 internal nodes ]
refseq()      DNAStringSet:      [ 6391 reference sequences ]

Phylogenetic tree was computed with the package phangorn, and the script was run on a cluster. Let’s check we have correctly generated a phylogenetic tree.

# Look at the tree
plot_tree(physeq.agp, color = "host_disease", ladderize="left")

ABUNDANCES

1. Absolute abundances

# Plot Phylum
plot_bar(physeq.agp, fill = "Phylum") + facet_wrap("host_disease", scales="free") +
  theme(axis.text.x = element_blank())+
  labs(x = "Samples", y = "Absolute abundance", title = "AGP dataset")

Sequencing depth characteristics of the Zhuang dataset:
- minimum of 578 total count per sample
- median: 2.328410^{4} total count per sample
- maximum of 4.5417410^{5} total count per sample

2. Relative abundances

3. Remove bloom sequences

Looking at it more closely, there is a high proportion of Proteobacteria compared to other datasets. Considering that fecal samples were shipped from the participants’ home at room temperature, there is evidence that some bacteria grow at room temperature, which would bias the microbial composition (https://journals.asm.org/doi/10.1128/mSystems.00199-16#B5). Authors from the AGP removed a selection of bloom sequences that were shared on their github repository in a FASTA file.

3. Firmicutes/Bacteroidota ratio

NORMALIZE DATA

# Sanity check no sample with less than 500 total count
table(sample_sums(physeq.filt)<500) # all FALSE

#____________________________________________________________________
# PHYLOSEQ OBJECT WITH NON-ZERO COMPOSITIONS
physeq.NZcomp <- physeq.filt
otu_table(physeq.NZcomp)[otu_table(physeq.NZcomp) == 0] <- 0.5 # pseudocounts

# Sanity check that 0 values have been replaced
# otu_table(physeq.filt)[1:5,1:5]
# otu_table(physeq.NZcomp)[1:5,1:5]

# transform into compositions
physeq.NZcomp <- transform_sample_counts(physeq.NZcomp, function(x) x / sum(x) )
table(rowSums(otu_table(physeq.NZcomp))) # check if there is any row not summing to 1

# Save object
saveRDS(physeq.NZcomp, file.path(path, "data/analysis-individual/AGP/02_EDA-AGP/physeq_NZcomp.rds"))

#____________________________________________________________________
# PHYLOSEQ OBJECT WITH RELATIVE COUNT (BETWEEN 0 AND 1)
physeq.rel <- physeq.filt
physeq.rel <- transform_sample_counts(physeq.rel, function(x) x / sum(x) ) # divide each count by the total number of counts (per sample)

# check the counts are all relative
# otu_table(physeq.filt)[1:5, 1:5]
# otu_table(physeq.rel)[1:5, 1:5]

# sanity check
table(rowSums(otu_table(physeq.rel))) # check if there is any row not summing to 1

# save the physeq.rel object
saveRDS(physeq.rel, file.path(path, "data/analysis-individual/AGP/02_EDA-AGP/physeq_relative.rds"))

#____________________________________________________________________
# PHYLOSEQ OBJECT WITH COMMON-SCALE NORMALIZATION
physeq.CSN <- physeq.filt
physeq.CSN <- transform_sample_counts(physeq.CSN, function(x) (x*min(sample_sums(physeq.CSN))) / sum(x) )

# sanity check
table(rowSums(otu_table(physeq.CSN))) # check that all rows are summing to the same total

# save the physeq.CSN object
saveRDS(physeq.CSN, file.path(path, "data/analysis-individual/AGP/02_EDA-AGP/physeq_CSN.rds"))


#____________________________________________________________________
# PHYLOSEQ OBJECT WITH CENTERED LOG RATIO COUNT
physeq.clr <- physeq.filt
physeq.clr <- microbiome::transform(physeq.filt, "clr") # the function adds pseudocounts itself

# Compare the otu tables in the original phyloseq object and the new one after CLR transformation
otu_table(physeq.filt)[1:5, 1:5] # should contain absolute counts
otu_table(physeq.clr)[1:5, 1:5] # should all be relative

# save the physeq.rel object
saveRDS(physeq.clr, file.path(path, "data/analysis-individual/AGP/02_EDA-AGP/physeq_clr.rds"))

COMPUTE DISTANCES

1. UniFrac, Aitchison, Bray-Curtis and Canberra

First, let’s look at these four distances of interest.

Now let’s plot!

# Get the distances & the plot data
dist.agp <- getDistances()
plot.df <- plotDistances2D(dist.agp)
# Plot
ggplot(plot.df, aes(Axis.1, Axis.2, color=host_disease))+
  geom_point(size=2, alpha=0.5)  + scale_color_manual(values = c('blue', 'red'))+
  facet_wrap(distance~., scales='free', nrow=1)+
  theme_bw()+
  theme(strip.text.x = element_text(size=20))+
  labs(color="Disease")

# ggsave(file.path(path.plots, "distances4_MDS.jpg"), height = 4, width = 15)

2. Plot in 3D

For better visualization, we will also take a glance at reduction to 3D.

Now let’s plot!

plotDistances3D(dist.agp$UniF, "UniFrac")
plotDistances3D(dist.agp$Ait, "Aitchison")
plotDistances3D(dist.agp$Canb, "Canberra")
plotDistances3D(dist.agp$Bray, "Bray-Curtis")

HIERARCHICAL CLUSTERING

# For heatmaps: have group color
matcol <- data.frame(group = sample_data(physeq.filt)[,"host_disease"])


# Function to get heatmap from the distances computed
plotHeatmaps <- function(dlist, fontsize){
  
  # Initialize variables
  i=1
  plist <- vector("list", 4)
  names(plist) <- names(dlist)
  
  # Loop through distances
  for(d in dlist){
    plist[[i]] <- pheatmap(as.matrix(d), 
                          clustering_distance_rows = d,
                          clustering_distance_cols = d,
                          fontsize = fontsize,
                          fontsize_col = fontsize-5,
                          fontsize_row = fontsize-5,
                          annotation_col = matcol,
                          annotation_row = matcol,
                          annotation_colors = list(host_disease = c('Healthy' = 'blue', 'IBS' = 'red')),
                          cluster_rows = T,
                          cluster_cols = T,
                          clustering_method = 'complete', # hc method
                          main = names(dlist)[i]) # have name of distance as title
    i <- i+1
  }
  
  return(plist)
}


# Get the heatmaps
heatmp.agp <- plotHeatmaps(dlist = dist.agp, fontsize = 8)

LS0tCnRpdGxlOiAiQUdQX0RhdGFBbmFseXNpcyIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogeWVzCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCi0tLQoKYGBge3IsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcud2lkdGggPSAxNSwgZmlnLmhlaWdodCA9IDUsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICByb290LmRpciA9ICJ+L1Byb2plY3RzL0lCU19NZXRhLWFuYWx5c2lzXzE2Uy9hbmFseXNpcy1pbmRpdmlkdWFsL0FHUC8iKQpgYGAKCgoKIyMjIyMjIyMjIyMjCiMjIElNUE9SVCAjIwojIyMjIyMjIyMjIyMKCiMjIExpYnJhaXJpZXMKCmBgYHtyIGxpYnJhcnktaW1wb3J0fQpsaWJyYXJ5KHBoeWxvc2VxKSAjIGZvciBwaHlsb3NlcSBvYmplY3QKbGlicmFyeShCaW9zdHJpbmdzKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoY293cGxvdCkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoInBsb3RseSIpICMgcGxvdCAzRApsaWJyYXJ5KCJtaWNyb2Jpb21lIikgIyBmb3IgY2VudGVyZWQgbG9nLXJhdGlvCmxpYnJhcnkoImNvZGEiKSAjIEFpdGNoaXNvbiBkaXN0YW5jZQpsaWJyYXJ5KCJjb2RhLmJhc2UiKSAjIEFpdGNoaXNvbiBkaXN0YW5jZQpsaWJyYXJ5KCJ2ZWdhbiIpICMgTk1EUwpsaWJyYXJ5KHBoZWF0bWFwKSAjIGZvciBoZWF0bWFwCmBgYAoKCiMjIERhdGEKCmBgYHtyIGltcG9ydC1kYXRhfQojIFNldCBwYXRoCnBhdGggPC0gIn4vUHJvamVjdHMvSUJTX01ldGEtYW5hbHlzaXNfMTZTIgoKIyBJbXBvcnQgcGh5bG9zZXEgb2JqZWN0CiMgcGh5c2VxLmFncCA8LSByZWFkUkRTKGZpbGUucGF0aChwYXRoLCAicGh5bG9zZXEtb2JqZWN0cy9waHlzZXFfQUdQZmlsdC5yZHMiKSkKcGh5c2VxLmFncCA8LSByZWFkUkRTKGZpbGUucGF0aChwYXRoLCAiZGF0YS9PdXRwdXRQaGFuZ29ybi9waHlzZXFfQUdQZmlsdC5yZHMiKSkKcGh5c2VxLmFncCA8LSBwcnVuZV9zYW1wbGVzKHNhbXBsZV9zdW1zKHBoeXNlcS5hZ3ApPj01MDAsIHBoeXNlcS5hZ3ApCgojIFNhbml0eSBjaGVjawpwaHlzZXEuYWdwCmBgYAoKUGh5bG9nZW5ldGljIHRyZWUgd2FzIGNvbXB1dGVkIHdpdGggdGhlIHBhY2thZ2UgYHBoYW5nb3JuYCwgYW5kIHRoZSBzY3JpcHQgd2FzIHJ1biBvbiBhIGNsdXN0ZXIuIExldCdzIGNoZWNrIHdlIGhhdmUgY29ycmVjdGx5IGdlbmVyYXRlZCBhIHBoeWxvZ2VuZXRpYyB0cmVlLgoKYGBge3IgcGxvdC10cmVlLCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gMTB9CiMgTG9vayBhdCB0aGUgdHJlZQpwbG90X3RyZWUocGh5c2VxLmFncCwgY29sb3IgPSAiaG9zdF9kaXNlYXNlIiwgbGFkZGVyaXplPSJsZWZ0IikKYGBgCgoKYGBge3IgaW1wb3J0LWZvci1wZGYsIGVjaG8gPSBGQUxTRX0KI19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fCiNfX19fX19fX19fX18gVEhJUyBJUyBPTkxZIEZPUiAocXVpY2tlcikgUERGL0hUTUwgT1VUUFVUIF9fX19fX19fX19fX19fX19fXwojX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18KCiMgSW1wb3J0IHBoeWxvc2VxIG9iamVjdHMgZm9yIGNvbXB1dGluZyBkaXN0YW5jZXMKcGh5c2VxLnJlbCA8LSByZWFkUkRTKGZpbGUucGF0aChwYXRoLCAiZGF0YS9hbmFseXNpcy1pbmRpdmlkdWFsL0FHUC8wMl9FREEtQUdQL3BoeXNlcV9yZWxhdGl2ZS5yZHMiKSkKcGh5c2VxLmNsciA8LSByZWFkUkRTKGZpbGUucGF0aChwYXRoLCAiZGF0YS9hbmFseXNpcy1pbmRpdmlkdWFsL0FHUC8wMl9FREEtQUdQL3BoeXNlcV9jbHIucmRzIikpCnBoeXNlcS5DU04gPC0gcmVhZFJEUyhmaWxlLnBhdGgocGF0aCwgImRhdGEvYW5hbHlzaXMtaW5kaXZpZHVhbC9BR1AvMDJfRURBLUFHUC9waHlzZXFfQ1NOLnJkcyIpKQpwaHlzZXEuTlpjb21wIDwtIHJlYWRSRFMoZmlsZS5wYXRoKHBhdGgsICJkYXRhL2FuYWx5c2lzLWluZGl2aWR1YWwvQUdQLzAyX0VEQS1BR1AvcGh5c2VxX05aY29tcC5yZHMiKSkKCmRpc3QuYWdwIDwtIHJlYWRSRFMoZmlsZS5wYXRoKHBhdGgsICJkYXRhL2FuYWx5c2lzLWluZGl2aWR1YWwvQUdQLzAyX0VEQS1BR1AvZGlzdGFuY2VzLnJkcyIpKQpwbG90LmRmIDwtIHJlYWRSRFMoZmlsZS5wYXRoKHBhdGgsICJkYXRhL2FuYWx5c2lzLWluZGl2aWR1YWwvQUdQLzAyX0VEQS1BR1AvcGxvdF9kaXN0YW5jZXMucmRzIikpCmBgYAoKCiMjIyMjIyMjIyMjIyMjIyMKIyMgQUJVTkRBTkNFUyAjIwojIyMjIyMjIyMjIyMjIyMjCgojIyMgMS4gQWJzb2x1dGUgYWJ1bmRhbmNlcwoKYGBge3IgcGxvdC1hYnNvbHV0ZS1hYnVuZGFuY2UsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodCA9IDV9CiMgUGxvdCBQaHlsdW0KcGxvdF9iYXIocGh5c2VxLmFncCwgZmlsbCA9ICJQaHlsdW0iKSArIGZhY2V0X3dyYXAoImhvc3RfZGlzZWFzZSIsIHNjYWxlcz0iZnJlZSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkrCiAgbGFicyh4ID0gIlNhbXBsZXMiLCB5ID0gIkFic29sdXRlIGFidW5kYW5jZSIsIHRpdGxlID0gIkFHUCBkYXRhc2V0IikKYGBgCgoqKlNlcXVlbmNpbmcgZGVwdGgqKiBjaGFyYWN0ZXJpc3RpY3Mgb2YgdGhlIFpodWFuZyBkYXRhc2V0OiAgCi0gbWluaW11bSBvZiBgciBtaW4oc2FtcGxlX3N1bXMocGh5c2VxLmFncCkpYCB0b3RhbCBjb3VudCBwZXIgc2FtcGxlICAKLSBtZWRpYW46IGByIG1lZGlhbihzYW1wbGVfc3VtcyhwaHlzZXEuYWdwKSlgIHRvdGFsIGNvdW50IHBlciBzYW1wbGUgIAotIG1heGltdW0gb2YgYHIgbWF4KHNhbXBsZV9zdW1zKHBoeXNlcS5hZ3ApKWAgdG90YWwgY291bnQgcGVyIHNhbXBsZSAgCgoKIyMjIDIuIFJlbGF0aXZlIGFidW5kYW5jZXMKCmBgYHtyIHBsb3QtcmVsYXRpdmUtYWJ1bmRhbmNlc30KIyBBZ2dsb21lcmF0ZSB0byBwaHlsdW0gJiBjbGFzcyBsZXZlbHMKcGh5bHVtLnRhYmxlIDwtIHBoeXNlcS5hZ3AgJT4lCiAgdGF4X2dsb20odGF4cmFuayA9ICJQaHlsdW0iKSAlPiUgICAgICAgICAgICAgICAgICAgICAjIGFnZ2xvbWVyYXRlIGF0IHBoeWx1bSBsZXZlbAogIHRyYW5zZm9ybV9zYW1wbGVfY291bnRzKGZ1bmN0aW9uKHgpIHt4L3N1bSh4KX0gKSAlPiUgIyBUcmFuc2Zvcm0gdG8gcmVsLiBhYnVuZGFuY2UKICBwc21lbHQoKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgTWVsdCB0byBsb25nIGZvcm1hdAoKY2xhc3MudGFibGUgPC0gcGh5c2VxLmFncCAlPiUKICB0YXhfZ2xvbSh0YXhyYW5rID0gIkNsYXNzIikgJT4lCiAgdHJhbnNmb3JtX3NhbXBsZV9jb3VudHMoZnVuY3Rpb24oeCkge3gvc3VtKHgpfSApICU+JQogIHBzbWVsdCgpCgoKIyBQbG90IHJlbGF0aXZlIGFidW5kYW5jZXMKZ2dwbG90KHBoeWx1bS50YWJsZSwgYWVzKHggPSByZW9yZGVyKFNhbXBsZSwgU2FtcGxlLCBmdW5jdGlvbih4KSBtZWFuKHBoeWx1bS50YWJsZVtTYW1wbGUgPT0geCAmIFBoeWx1bSA9PSAnQmFjdGVyb2lkb3RhJywgJ0FidW5kYW5jZSddKSksCiAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gQWJ1bmRhbmNlLCBmaWxsID0gUGh5bHVtKSkrCiAgZmFjZXRfd3JhcCh+IGhvc3RfZGlzZWFzZSwgc2NhbGVzID0gImZyZWUiKSArICMgc2NhbGVzID0gImZyZWUiIHJlbW92ZXMgZW1wdHkgbGluZXMKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSsKICBsYWJzKHggPSAiU2FtcGxlcyIsIHkgPSAiUmVsYXRpdmUgYWJ1bmRhbmNlIiwgdGl0bGUgPSAiQUdQIGRhdGFzZXQiKQoKZ2dwbG90KGNsYXNzLnRhYmxlLCBhZXMoeCA9IHJlb3JkZXIoU2FtcGxlLCBTYW1wbGUsIGZ1bmN0aW9uKHgpIG1lYW4ocGh5bHVtLnRhYmxlW1NhbXBsZSA9PSB4ICYgUGh5bHVtID09ICdCYWN0ZXJvaWRvdGEnLCAnQWJ1bmRhbmNlJ10pKSwKICAgICAgICAgICAgICAgICAgICAgICAgeSA9IEFidW5kYW5jZSwgZmlsbCA9IENsYXNzKSkrCiAgZmFjZXRfd3JhcCh+IGhvc3RfZGlzZWFzZSwgc2NhbGVzID0gImZyZWUiKSArICMgc2NhbGVzID0gImZyZWUiIHJlbW92ZXMgZW1wdHkgbGluZXMKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSsKICBsYWJzKHggPSAiU2FtcGxlcyIsIHkgPSAiUmVsYXRpdmUgYWJ1bmRhbmNlIiwgdGl0bGUgPSAiQUdQIGRhdGFzZXQiKQpgYGAKCgojIyMgMy4gUmVtb3ZlIGJsb29tIHNlcXVlbmNlcwoKTG9va2luZyBhdCBpdCBtb3JlIGNsb3NlbHksIHRoZXJlIGlzIGEgaGlnaCBwcm9wb3J0aW9uIG9mIFByb3Rlb2JhY3RlcmlhIGNvbXBhcmVkIHRvIG90aGVyIGRhdGFzZXRzLiBDb25zaWRlcmluZyB0aGF0IGZlY2FsIHNhbXBsZXMgd2VyZSBzaGlwcGVkIGZyb20gdGhlIHBhcnRpY2lwYW50cycgaG9tZSBhdCByb29tIHRlbXBlcmF0dXJlLCB0aGVyZSBpcyBldmlkZW5jZSB0aGF0IHNvbWUgYmFjdGVyaWEgZ3JvdyBhdCByb29tIHRlbXBlcmF0dXJlLCB3aGljaCB3b3VsZCBiaWFzIHRoZSBtaWNyb2JpYWwgY29tcG9zaXRpb24gKGh0dHBzOi8vam91cm5hbHMuYXNtLm9yZy9kb2kvMTAuMTEyOC9tU3lzdGVtcy4wMDE5OS0xNiNCNSkuIEF1dGhvcnMgZnJvbSB0aGUgQUdQIHJlbW92ZWQgYSBzZWxlY3Rpb24gb2YgKipibG9vbSBzZXF1ZW5jZXMqKiB0aGF0IHdlcmUgc2hhcmVkIG9uIHRoZWlyIFtnaXRodWIgcmVwb3NpdG9yeV1bMV0gaW4gYSBGQVNUQSBmaWxlLgoKWzFdOiBodHRwczovL2dpdGh1Yi5jb20va25pZ2h0bGFiLWFuYWx5c2VzL2Jsb29tLWFuYWx5c2VzCgpgYGB7ciBibG9vbS1zZXF1ZW5jZXN9CiMgSW1wb3J0IGJsb29tIHNlcXVlbmNlcwpibG9vbSA8LSByZWFkRE5BU3RyaW5nU2V0KGZpbGUucGF0aChwYXRoLCAiZGF0YS9hbmFseXNpcy1pbmRpdmlkdWFsL0FHUC9uZXdibG9vbS5hbGwuZm5hIikpCgojIElkZW50aWZ5IEFTVnMgdG8ga2VlcCAob25lcyB0aGF0IGFyZSBub3QgYmxvb20gc2VxdWVuY2VzKQojIG5vdGU6IHRyaW0gQVNWcyB0byAxNTBicCBhcyBibG9vbSBzZXF1ZW5jZXMgYXJlIDE1MGJwIChhIGZldyBBU1ZzIGFyZSAxNTFicCkKQVNWc190b19rZWVwIDwtIG5hbWVzKHNldGRpZmYoc3Vic2VxKHJlZnNlcShwaHlzZXEuYWdwKSwgc3RhcnQ9MSwgd2lkdGg9MTUwKSwgYmxvb20pKQoKIyBDaGVjayB0YXhvbm9taWMgY2xhc3NpZmljYXRpb24gb2YgdGhlc2UgYmxvb20gc2VxdWVuY2VzCnRheF90YWJsZShwaHlzZXEuYWdwKSAlPiUKICBhcy5kYXRhLmZyYW1lICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbih2YXI9IkFTViIpICU+JQogIGZpbHRlcighQVNWICVpbiUgQVNWc190b19rZWVwKSAlPiUKICBncm91cF9ieShDbGFzcykgJT4lCiAgc3VtbWFyaXplKG49bigpKQoKIyBSZW1vdmUgdW53YW50ZWQgdGF4YSAoYmxvb20pCnBoeXNlcS5maWx0IDwtIHBydW5lX3RheGEoQVNWc190b19rZWVwLCBwaHlzZXEuYWdwKQpwaHlzZXEuZmlsdCA8LSBwcnVuZV9zYW1wbGVzKHNhbXBsZV9zdW1zKHBoeXNlcS5maWx0KT49NTAwLCBwaHlzZXEuZmlsdCkKCnBoeWx1bS50YWJsZS5maWx0IDwtIHBoeXNlcS5maWx0ICU+JQogIHRheF9nbG9tKHRheHJhbmsgPSAiUGh5bHVtIikgJT4lICAgICAgICAgICAgICAgICAgICAgIyBhZ2dsb21lcmF0ZSBhdCBwaHlsdW0gbGV2ZWwKICB0cmFuc2Zvcm1fc2FtcGxlX2NvdW50cyhmdW5jdGlvbih4KSB7eC9zdW0oeCl9ICkgJT4lICMgVHJhbnNmb3JtIHRvIHJlbC4gYWJ1bmRhbmNlCiAgcHNtZWx0KCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIE1lbHQgdG8gbG9uZyBmb3JtYXQKCgojIFBMT1QKYSA8LSBnZ3Bsb3QocGh5bHVtLnRhYmxlLCBhZXMoeCA9IGhvc3RfZGlzZWFzZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IEFidW5kYW5jZSwgZmlsbCA9IFBoeWx1bSkpKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikrCiAgbGFicyh4ID0gIiIsIHkgPSAiUmVsYXRpdmUgYWJ1bmRhbmNlIiwgdGl0bGUgPSAiT3JpZ2luYWwgZGF0YSIpCgpiIDwtIGdncGxvdChwaHlsdW0udGFibGUuZmlsdCwgYWVzKHggPSBob3N0X2Rpc2Vhc2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IEFidW5kYW5jZSwgZmlsbCA9IFBoeWx1bSkpKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgIyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkrCiAgbGFicyh4ID0gIiIsIHkgPSAiUmVsYXRpdmUgYWJ1bmRhbmNlIiwgdGl0bGUgPSAiQmxvb20gc2VxdWVuY2VzIHJlbW92ZWQiKQoKZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UoYSwgYiwgbmNvbD0yLCB3aWR0aHM9YygwLjQsMC42KSkKYGBgCgoKIyMjIDMuIEZpcm1pY3V0ZXMvQmFjdGVyb2lkb3RhIHJhdGlvCgpgYGB7ciBwbG90LWxvZy1yYXRpbywgZmlnLmhlaWdodCA9IDUsIGZpZy53aWR0aD0zfQojIEV4dHJhY3QgYWJ1bmRhbmNlIG9mIG9ubHkgQmFjdGVyb2lkb3RhIGFuZCBGaXJtaWN1dGVzCmJhY3RlciA8LSBwaHlsdW0udGFibGUuZmlsdCAlPiUKICBmaWx0ZXIoUGh5bHVtID09ICJCYWN0ZXJvaWRvdGEiKSAlPiUKICBzZWxlY3QoYygnU2FtcGxlJywgJ0FidW5kYW5jZScsICdob3N0X2Rpc2Vhc2UnLCAnUGh5bHVtJykpICU+JQogIGFycmFuZ2UoU2FtcGxlKQoKZmlybWkgPC0gcGh5bHVtLnRhYmxlLmZpbHQgJT4lCiAgZmlsdGVyKFBoeWx1bSA9PSAiRmlybWljdXRlcyIpICU+JQogIHNlbGVjdChjKCdTYW1wbGUnLCAnQWJ1bmRhbmNlJywgJ2hvc3RfZGlzZWFzZScsICdQaHlsdW0nKSkgJT4lCiAgYXJyYW5nZShTYW1wbGUpCgojIENhbGN1bGF0ZSBsb2cyIHJhdGlvIEZpcm1pY3V0ZXMvQmFjdGVyb2lkb3RhCnJhdGlvLkZCIDwtIGRhdGEuZnJhbWUoJ1NhbXBsZScgPSBiYWN0ZXIkU2FtcGxlLAogICAgICAgICAgICAgICAgICAgICAgICdob3N0X2Rpc2Vhc2UnID0gYmFjdGVyJGhvc3RfZGlzZWFzZSwKICAgICAgICAgICAgICAgICAgICAgICAnQmFjdGVyb2lkb3RhJyA9IGJhY3RlciRBYnVuZGFuY2UsCiAgICAgICAgICAgICAgICAgICAgICAgJ0Zpcm1pY3V0ZXMnID0gZmlybWkkQWJ1bmRhbmNlKQpyYXRpby5GQiRsb2dSYXRpb0ZCIDwtIGxvZzIocmF0aW8uRkIkRmlybWljdXRlcyAvIHJhdGlvLkZCJEJhY3Rlcm9pZG90YSkKCiMgUGxvdCBsb2cyIHJhdGlvIEZpcm1pY3V0ZXMvQmFjdGVyb2lkb3RhCmdncGxvdChyYXRpby5GQiwgYWVzKHggPSBob3N0X2Rpc2Vhc2UsIHkgPSBsb2dSYXRpb0ZCKSkrCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGUgPSBOQSkrCiAgZ2VvbV9qaXR0ZXIod2lkdGg9MC4xKSsKICBsYWJzKHggPSAiIiwgIHkgPSAnTG9nMihGaXJtaWN1dGVzL0JhY3Rlcm9pZG90YSknLCB0aXRsZSA9ICJGaXJtaWN1dGVzOkJhY3Rlcm9pZG90YSByYXRpbyIpKwogIHRoZW1lX2Nvd3Bsb3QoKQpgYGAKCgpgYGB7ciBleHBvcnQtcGxvdHMtMSwgZXZhbCA9IEZBTFNFLCBlY2hvID0gRkFMU0V9CiMgU2F2ZSBwYXRoIHRvIHBsb3RzCnBhdGgucGxvdHMgPC0gZmlsZS5wYXRoKHBhdGgsICJkYXRhL2FuYWx5c2lzLWluZGl2aWR1YWwvUExPVFMvcGxvdHMtQUdQLUVEQSIpCgojIEFic29sdXRlIGFidW5kYW5jZSBwaHlsdW0KanBlZyhmaWxlLnBhdGgocGF0aC5wbG90cywgImFic0FidW5kYW5jZV9waHlsdW0uanBnIiksIHdpZHRoID0gNDAwMCwgaGVpZ2h0ID0gMjAwMCkKcGxvdF9iYXIocGh5c2VxLmZpbHQsIGZpbGwgPSAiUGh5bHVtIikgKyBmYWNldF93cmFwKCJob3N0X2Rpc2Vhc2UiLCBzY2FsZXM9ImZyZWUiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDMwKSwKICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNTApLAogICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDUwKSwKICAgICAgICB0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gNzApLAogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDUwKSkrCiAgbGFicyh4ID0gIlNhbXBsZXMiLCB5ID0gIlRvdGFsIHJlYWQgY291bnQiLCB0aXRsZSA9ICJBR1AgZGF0YXNldCIpCmRldi5vZmYoKQoKIyBSZWxhdGl2ZSBhYnVuZGFuY2VzCmpwZWcoZmlsZS5wYXRoKHBhdGgucGxvdHMsICJyZWxBYnVuZGFuY2VfcGh5bHVtLmpwZyIpLCB3aWR0aCA9IDQwMDAsIGhlaWdodCA9IDIwMDApCmdncGxvdChwaHlsdW0udGFibGUuZmlsdCwgYWVzKHggPSByZW9yZGVyKFNhbXBsZSwgU2FtcGxlLCBmdW5jdGlvbih4KSBtZWFuKHBoeWx1bS50YWJsZVtTYW1wbGUgPT0geCAmIFBoeWx1bSA9PSAnQmFjdGVyb2lkb3RhJywgJ0FidW5kYW5jZSddKSksCiAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gQWJ1bmRhbmNlLCBmaWxsID0gUGh5bHVtKSkrCiAgZmFjZXRfd3JhcCh+IGhvc3RfZGlzZWFzZSwgc2NhbGVzID0gImZyZWUiKSArICMgc2NhbGVzID0gImZyZWUiIHJlbW92ZXMgZW1wdHkgbGluZXMKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAzMCksCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDUwKSwKICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA1MCksCiAgICAgICAgdGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcwKSwKICAgICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA1MCkpKwogIGxhYnMoeCA9ICJTYW1wbGVzIiwgeSA9ICJSZWxhdGl2ZSBhYnVuZGFuY2UiLCB0aXRsZSA9ICJBR1AgZGF0YXNldCIpCmRldi5vZmYoKQoKanBlZyhmaWxlLnBhdGgocGF0aC5wbG90cywgInJlbEFidW5kYW5jZV9jbGFzcy5qcGciKSwgd2lkdGggPSA0MDAwLCBoZWlnaHQgPSAyMDAwKQpnZ3Bsb3QoY2xhc3MudGFibGUsIGFlcyh4ID0gcmVvcmRlcihTYW1wbGUsIFNhbXBsZSwgZnVuY3Rpb24oeCkgbWVhbihwaHlsdW0udGFibGVbU2FtcGxlID09IHggJiBQaHlsdW0gPT0gJ0JhY3Rlcm9pZG90YScsICdBYnVuZGFuY2UnXSkpLAogICAgICAgICAgICAgICAgICAgICAgICB5ID0gQWJ1bmRhbmNlLCBmaWxsID0gQ2xhc3MpKSsKICBmYWNldF93cmFwKH4gaG9zdF9kaXNlYXNlLCBzY2FsZXMgPSAiZnJlZSIpICsgIyBzY2FsZXMgPSAiZnJlZSIgcmVtb3ZlcyBlbXB0eSBsaW5lcwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDMwKSwKICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNTApLAogICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDUwKSwKICAgICAgICB0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gNzApLAogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDUwKSkrCiAgbGFicyh4ID0gIlNhbXBsZXMiLCB5ID0gIlJlbGF0aXZlIGFidW5kYW5jZSIsIHRpdGxlID0gIkFHUCBkYXRhc2V0IikKZGV2Lm9mZigpCgojIEZpcm1pY3V0ZXM6QmFjdGVyb2lkb3RhIGxvZyByYXRpbwpqcGVnKGZpbGUucGF0aChwYXRoLnBsb3RzLCAicmF0aW9GQi5qcGciKSwgd2lkdGggPSAxNTAwLCBoZWlnaHQgPSAyMDAwKQpnZ3Bsb3QocmF0aW8uRkIsIGFlcyh4ID0gaG9zdF9kaXNlYXNlLCB5ID0gbG9nUmF0aW9GQikpKwogIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlID0gTkEsIGx3ZCA9IDMpKwogIGdlb21faml0dGVyKHNpemUgPSA4LCB3aWR0aD0wLjEpKwogIGxhYnMoeCA9ICIiLCAgeSA9ICdMb2cyKEZpcm1pY3V0ZXMvQmFjdGVyb2lkb3RhKScsIHRpdGxlID0gIkFHUCBkYXRhc2V0IikrCiAgdGhlbWVfY2xhc3NpYygpKwogIHRoZW1lKGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNTApLAogICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA1MCksCiAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gNTApLAogICAgICAgIHRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA1MCkpCmRldi5vZmYoKQpgYGAKCgoKIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyMgTk9STUFMSVpFIERBVEEgIyMKIyMjIyMjIyMjIyMjIyMjIyMjIyMKCmBgYHtyIG5vcm1hbGl6ZS1kYXRhLCBldmFsID0gRkFMU0UsIGVjaG8gPSBUUlVFfQojIFNhbml0eSBjaGVjayBubyBzYW1wbGUgd2l0aCBsZXNzIHRoYW4gNTAwIHRvdGFsIGNvdW50CnRhYmxlKHNhbXBsZV9zdW1zKHBoeXNlcS5maWx0KTw1MDApICMgYWxsIEZBTFNFCgojX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18KIyBQSFlMT1NFUSBPQkpFQ1QgV0lUSCBOT04tWkVSTyBDT01QT1NJVElPTlMKcGh5c2VxLk5aY29tcCA8LSBwaHlzZXEuZmlsdApvdHVfdGFibGUocGh5c2VxLk5aY29tcClbb3R1X3RhYmxlKHBoeXNlcS5OWmNvbXApID09IDBdIDwtIDAuNSAjIHBzZXVkb2NvdW50cwoKIyBTYW5pdHkgY2hlY2sgdGhhdCAwIHZhbHVlcyBoYXZlIGJlZW4gcmVwbGFjZWQKIyBvdHVfdGFibGUocGh5c2VxLmZpbHQpWzE6NSwxOjVdCiMgb3R1X3RhYmxlKHBoeXNlcS5OWmNvbXApWzE6NSwxOjVdCgojIHRyYW5zZm9ybSBpbnRvIGNvbXBvc2l0aW9ucwpwaHlzZXEuTlpjb21wIDwtIHRyYW5zZm9ybV9zYW1wbGVfY291bnRzKHBoeXNlcS5OWmNvbXAsIGZ1bmN0aW9uKHgpIHggLyBzdW0oeCkgKQp0YWJsZShyb3dTdW1zKG90dV90YWJsZShwaHlzZXEuTlpjb21wKSkpICMgY2hlY2sgaWYgdGhlcmUgaXMgYW55IHJvdyBub3Qgc3VtbWluZyB0byAxCgojIFNhdmUgb2JqZWN0CnNhdmVSRFMocGh5c2VxLk5aY29tcCwgZmlsZS5wYXRoKHBhdGgsICJkYXRhL2FuYWx5c2lzLWluZGl2aWR1YWwvQUdQLzAyX0VEQS1BR1AvcGh5c2VxX05aY29tcC5yZHMiKSkKCiNfX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fXwojIFBIWUxPU0VRIE9CSkVDVCBXSVRIIFJFTEFUSVZFIENPVU5UIChCRVRXRUVOIDAgQU5EIDEpCnBoeXNlcS5yZWwgPC0gcGh5c2VxLmZpbHQKcGh5c2VxLnJlbCA8LSB0cmFuc2Zvcm1fc2FtcGxlX2NvdW50cyhwaHlzZXEucmVsLCBmdW5jdGlvbih4KSB4IC8gc3VtKHgpICkgIyBkaXZpZGUgZWFjaCBjb3VudCBieSB0aGUgdG90YWwgbnVtYmVyIG9mIGNvdW50cyAocGVyIHNhbXBsZSkKCiMgY2hlY2sgdGhlIGNvdW50cyBhcmUgYWxsIHJlbGF0aXZlCiMgb3R1X3RhYmxlKHBoeXNlcS5maWx0KVsxOjUsIDE6NV0KIyBvdHVfdGFibGUocGh5c2VxLnJlbClbMTo1LCAxOjVdCgojIHNhbml0eSBjaGVjawp0YWJsZShyb3dTdW1zKG90dV90YWJsZShwaHlzZXEucmVsKSkpICMgY2hlY2sgaWYgdGhlcmUgaXMgYW55IHJvdyBub3Qgc3VtbWluZyB0byAxCgojIHNhdmUgdGhlIHBoeXNlcS5yZWwgb2JqZWN0CnNhdmVSRFMocGh5c2VxLnJlbCwgZmlsZS5wYXRoKHBhdGgsICJkYXRhL2FuYWx5c2lzLWluZGl2aWR1YWwvQUdQLzAyX0VEQS1BR1AvcGh5c2VxX3JlbGF0aXZlLnJkcyIpKQoKI19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fCiMgUEhZTE9TRVEgT0JKRUNUIFdJVEggQ09NTU9OLVNDQUxFIE5PUk1BTElaQVRJT04KcGh5c2VxLkNTTiA8LSBwaHlzZXEuZmlsdApwaHlzZXEuQ1NOIDwtIHRyYW5zZm9ybV9zYW1wbGVfY291bnRzKHBoeXNlcS5DU04sIGZ1bmN0aW9uKHgpICh4Km1pbihzYW1wbGVfc3VtcyhwaHlzZXEuQ1NOKSkpIC8gc3VtKHgpICkKCiMgc2FuaXR5IGNoZWNrCnRhYmxlKHJvd1N1bXMob3R1X3RhYmxlKHBoeXNlcS5DU04pKSkgIyBjaGVjayB0aGF0IGFsbCByb3dzIGFyZSBzdW1taW5nIHRvIHRoZSBzYW1lIHRvdGFsCgojIHNhdmUgdGhlIHBoeXNlcS5DU04gb2JqZWN0CnNhdmVSRFMocGh5c2VxLkNTTiwgZmlsZS5wYXRoKHBhdGgsICJkYXRhL2FuYWx5c2lzLWluZGl2aWR1YWwvQUdQLzAyX0VEQS1BR1AvcGh5c2VxX0NTTi5yZHMiKSkKCgojX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18KIyBQSFlMT1NFUSBPQkpFQ1QgV0lUSCBDRU5URVJFRCBMT0cgUkFUSU8gQ09VTlQKcGh5c2VxLmNsciA8LSBwaHlzZXEuZmlsdApwaHlzZXEuY2xyIDwtIG1pY3JvYmlvbWU6OnRyYW5zZm9ybShwaHlzZXEuZmlsdCwgImNsciIpICMgdGhlIGZ1bmN0aW9uIGFkZHMgcHNldWRvY291bnRzIGl0c2VsZgoKIyBDb21wYXJlIHRoZSBvdHUgdGFibGVzIGluIHRoZSBvcmlnaW5hbCBwaHlsb3NlcSBvYmplY3QgYW5kIHRoZSBuZXcgb25lIGFmdGVyIENMUiB0cmFuc2Zvcm1hdGlvbgpvdHVfdGFibGUocGh5c2VxLmZpbHQpWzE6NSwgMTo1XSAjIHNob3VsZCBjb250YWluIGFic29sdXRlIGNvdW50cwpvdHVfdGFibGUocGh5c2VxLmNscilbMTo1LCAxOjVdICMgc2hvdWxkIGFsbCBiZSByZWxhdGl2ZQoKIyBzYXZlIHRoZSBwaHlzZXEucmVsIG9iamVjdApzYXZlUkRTKHBoeXNlcS5jbHIsIGZpbGUucGF0aChwYXRoLCAiZGF0YS9hbmFseXNpcy1pbmRpdmlkdWFsL0FHUC8wMl9FREEtQUdQL3BoeXNlcV9jbHIucmRzIikpCmBgYAoKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMjIENPTVBVVEUgRElTVEFOQ0VTICMjCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgojIyMgMS4gVW5pRnJhYywgQWl0Y2hpc29uLCBCcmF5LUN1cnRpcyBhbmQgQ2FuYmVycmEKCkZpcnN0LCBsZXQncyBsb29rIGF0IHRoZXNlIGZvdXIgZGlzdGFuY2VzIG9mIGludGVyZXN0LgoKYGBge3IgZGlzdC1mdW5jdGlvbnMsIGZpZy53aWR0aCA9IDE1LCBmaWcuaGVpZ2h0ID0gNH0KI19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fXwojIE1lYXN1cmUgZGlzdGFuY2VzCmdldERpc3RhbmNlcyA8LSBmdW5jdGlvbigpewogIHNldC5zZWVkKDEyMykgIyBmb3IgdW5pZnJhYywgbmVlZCB0byBzZXQgYSBzZWVkCiAgZ2xvbS5VbmlGIDwtIFVuaUZyYWMocGh5c2VxLnJlbCwgd2VpZ2h0ZWQ9VFJVRSwgbm9ybWFsaXplZD1UUlVFKSAjIHdlaWdodGVkIHVuaWZyYWMKICBnbG9tLmFpdCA8LSBwaHlsb3NlcTo6ZGlzdGFuY2UocGh5c2VxLmNsciwgbWV0aG9kID0gJ2V1Y2xpZGVhbicpICMgYWl0Y2hpc29uCiAgZ2xvbS5icmF5IDwtIHBoeWxvc2VxOjpkaXN0YW5jZShwaHlzZXEuQ1NOLCBtZXRob2QgPSAiYnJheSIpICMgYnJheS1jdXJ0aXMKICBnbG9tLmNhbiA8LSBwaHlsb3NlcTo6ZGlzdGFuY2UocGh5c2VxLk5aY29tcCwgbWV0aG9kID0gImNhbmJlcnJhIikgIyBjYW5iZXJyYQogIGRpc3QubGlzdCA8LSBsaXN0KCJVbmlGIiA9IGdsb20uVW5pRiwgIkFpdCIgPSBnbG9tLmFpdCwgIkNhbmIiID0gZ2xvbS5jYW4sICJCcmF5IiA9IGdsb20uYnJheSkKICAKICByZXR1cm4oZGlzdC5saXN0KQp9CgoKI19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fXwojIFBsb3QgaW4gMkQgdGhlIGRpc3RhbmNlcwpwbG90RGlzdGFuY2VzMkQgPC0gZnVuY3Rpb24oZGxpc3QsIG9yZGluYXRpb249Ik1EUyIpewogIHBsaXN0IDwtIE5VTEwKICBwbGlzdCA8LSB2ZWN0b3IoImxpc3QiLCA0KQogIG5hbWVzKHBsaXN0KSA8LSBjKCJXZWlnaHRlZCBVbmlmcmFjIiwgIkFpdGNoaXNvbiIsICJCcmF5LUN1cnRpcyIsICJDYW5iZXJyYSIpCiAgCiAgcHJpbnQoIlVuaWZyYWMiKQogICMgV2VpZ2h0ZWQgVW5pRnJhYwogIHNldC5zZWVkKDEyMykKICBpTURTLlVuaUYgPC0gb3JkaW5hdGUocGh5c2VxLnJlbCwgb3JkaW5hdGlvbiwgZGlzdGFuY2U9ZGxpc3QkVW5pRikKICBwbGlzdFtbMV1dIDwtIHBsb3Rfb3JkaW5hdGlvbihwaHlzZXEucmVsLCBpTURTLlVuaUYsIGNvbG9yPSJob3N0X2Rpc2Vhc2UiKQogIAogIHByaW50KCJBaXRjaGlzb24iKQogICMgQWl0Y2hpc29uCiAgc2V0LnNlZWQoMTIzKQogIGlNRFMuQWl0IDwtIG9yZGluYXRlKHBoeXNlcS5jbHIsIG9yZGluYXRpb24sIGRpc3RhbmNlPWRsaXN0JEFpdCkKICBwbGlzdFtbMl1dIDwtIHBsb3Rfb3JkaW5hdGlvbihwaHlzZXEuY2xyLCBpTURTLkFpdCwgY29sb3I9Imhvc3RfZGlzZWFzZSIpCiAgCiAgcHJpbnQoIkJyYXkiKQogICMgQnJheS1DdXJ0aXMKICBzZXQuc2VlZCgxMjMpCiAgaU1EUy5CcmF5IDwtIG9yZGluYXRlKHBoeXNlcS5DU04sIG9yZGluYXRpb24sIGRpc3RhbmNlPWRsaXN0JEJyYXkpCiAgcGxpc3RbWzNdXSA8LSBwbG90X29yZGluYXRpb24ocGh5c2VxLkNTTiwgaU1EUy5CcmF5LCBjb2xvcj0iaG9zdF9kaXNlYXNlIikKICAKICBwcmludCgiQ2FuYmVycmEiKQogICMgQ2FuYmVycmEKICBzZXQuc2VlZCgxMjMpCiAgaU1EUy5DYW4gPC0gb3JkaW5hdGUocGh5c2VxLk5aY29tcCwgb3JkaW5hdGlvbiwgZGlzdGFuY2U9ZGxpc3QkQ2FuKQogIHBsaXN0W1s0XV0gPC0gcGxvdF9vcmRpbmF0aW9uKHBoeXNlcS5OWmNvbXAsIGlNRFMuQ2FuLCBjb2xvcj0iaG9zdF9kaXNlYXNlIikKICAKICAjIENyZWF0aW5nIGEgZGF0YWZyYW1lIHRvIHBsb3QgZXZlcnl0aGluZwogIHBsb3QuZGYgPSBsZHBseShwbGlzdCwgZnVuY3Rpb24oeCkgeCRkYXRhKQogIG5hbWVzKHBsb3QuZGYpWzFdIDwtICJkaXN0YW5jZSIKICAKICByZXR1cm4ocGxvdC5kZikKfQpgYGAKCgpOb3cgbGV0J3MgcGxvdCEKCmBgYHtyIGRpc3QtcnVuLTEsIGVjaG89VFJVRSwgZXZhbD1GQUxTRX0KIyBHZXQgdGhlIGRpc3RhbmNlcyAmIHRoZSBwbG90IGRhdGEKZGlzdC5hZ3AgPC0gZ2V0RGlzdGFuY2VzKCkKcGxvdC5kZiA8LSBwbG90RGlzdGFuY2VzMkQoZGlzdC5hZ3ApCmBgYAoKYGBge3IgZGlzdC1ydW4tMn0KIyBQbG90CmdncGxvdChwbG90LmRmLCBhZXMoQXhpcy4xLCBBeGlzLjIsIGNvbG9yPWhvc3RfZGlzZWFzZSkpKwogIGdlb21fcG9pbnQoc2l6ZT0yLCBhbHBoYT0wLjUpICArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCdibHVlJywgJ3JlZCcpKSsKICBmYWNldF93cmFwKGRpc3RhbmNlfi4sIHNjYWxlcz0nZnJlZScsIG5yb3c9MSkrCiAgdGhlbWVfYncoKSsKICB0aGVtZShzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZT0yMCkpKwogIGxhYnMoY29sb3I9IkRpc2Vhc2UiKQoKIyBnZ3NhdmUoZmlsZS5wYXRoKHBhdGgucGxvdHMsICJkaXN0YW5jZXM0X01EUy5qcGciKSwgaGVpZ2h0ID0gNCwgd2lkdGggPSAxNSkKYGBgCgoKYGBge3IgdG9uZywgZWNobz1GQUxTRSwgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KIyBTaGFyZSB3aXRoIFRvbmcKcGxvdF9vcmRpbmF0aW9uKHBoeXNlcS5DU04sCiAgICAgICAgICAgICAgICBvcmRpbmF0ZShwaHlzZXEuQ1NOLCAiUENvQSIsIGRpc3RhbmNlPWRpc3QuYWdwJEJyYXkpLAogICAgICAgICAgICAgICAgY29sb3I9Imhvc3RfZGlzZWFzZSIpICsKICB0aGVtZV9idygpCmdnc2F2ZShmaWxlLnBhdGgocGF0aC5wbG90cywgIlBDb0FfYnJheS5qcGciKSwgaGVpZ2h0ID0gNSwgd2lkdGggPSA3KQpgYGAKCgoKIyMjIDIuIFBsb3QgaW4gM0QKCkZvciBiZXR0ZXIgdmlzdWFsaXphdGlvbiwgd2Ugd2lsbCBhbHNvIHRha2UgYSBnbGFuY2UgYXQgcmVkdWN0aW9uIHRvIDNELgoKYGBge3IgM0QtZnVuY3Rpb24sIGV2YWwgPSBUUlVFLCBlY2hvID0gVFJVRX0KI19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fXwojIFBsb3QgM0Qgb3JkaW5hdGlvbgpwbG90RGlzdGFuY2VzM0QgPC0gZnVuY3Rpb24oZCwgbmFtZV9kaXN0KXsKICAKICAjIFJlc2V0IHBhcmFtZXRlcnMKICBtZHMuM0QgPC0gTlVMTAogIHh5eiA8LSBOVUxMCiAgZmlnLjNEIDwtIE5VTEwKICAKICAjIFJlZHVjZSBkaXN0YW5jZSBtYXRyaXggdG8gMyBkaW1lbnNpb25zCiAgc2V0LnNlZWQoMTIzKQogIG1kcy4zRCA8LSBtZXRhTURTKGQsIG1ldGhvZD0iTURTIiwgaz0zLCB0cmFjZSA9IDApCiAgeHl6IDwtIHNjb3JlcyhtZHMuM0QsIGRpc3BsYXk9InNpdGVzIikgIyBwdWxsIG91dCB0aGUgKHgseSx6KSBjb29yZGluYXRlcwogIAogICMgUGxvdAogIGZpZy4zRCA8LSBwbG90X2x5KHg9eHl6WywxXSwgeT14eXpbLDJdLCB6PXh5elssM10sIHR5cGU9InNjYXR0ZXIzZCIsIG1vZGU9Im1hcmtlcnMiLAogICAgICAgICAgICAgICAgICAgIGNvbG9yPXNhbXBsZV9kYXRhKHBoeXNlcS5maWx0KSRob3N0X2Rpc2Vhc2UsIGNvbG9ycyA9IGMoImJsdWUiLCAicmVkIikpJT4lCiAgICBsYXlvdXQodGl0bGUgPSBwYXN0ZSgnTURTIGluIDNEIHdpdGgnLCBuYW1lX2Rpc3QsICdkaXN0YW5jZScsIHNlcCA9ICcgJykpCiAgCiAgcmV0dXJuKGZpZy4zRCkKfQoKYGBgCgoKTm93IGxldCdzIHBsb3QhCgpgYGB7ciAzRC1wbG90LCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNX0KcGxvdERpc3RhbmNlczNEKGRpc3QuYWdwJFVuaUYsICJVbmlGcmFjIikKcGxvdERpc3RhbmNlczNEKGRpc3QuYWdwJEFpdCwgIkFpdGNoaXNvbiIpCnBsb3REaXN0YW5jZXMzRChkaXN0LmFncCRDYW5iLCAiQ2FuYmVycmEiKQpwbG90RGlzdGFuY2VzM0QoZGlzdC5hZ3AkQnJheSwgIkJyYXktQ3VydGlzIikKYGBgCgoKCgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojIyBISUVSQVJDSElDQUwgQ0xVU1RFUklORyAjIwojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKYGBge3IgaGllcmFyY2hpY2FsLWNsdXN0LCBmaWcud2lkdGggPSAyNSwgZmlnLndpZHRoID0gN30KIyBGb3IgaGVhdG1hcHM6IGhhdmUgZ3JvdXAgY29sb3IKbWF0Y29sIDwtIGRhdGEuZnJhbWUoZ3JvdXAgPSBzYW1wbGVfZGF0YShwaHlzZXEuZmlsdClbLCJob3N0X2Rpc2Vhc2UiXSkKCgojIEZ1bmN0aW9uIHRvIGdldCBoZWF0bWFwIGZyb20gdGhlIGRpc3RhbmNlcyBjb21wdXRlZApwbG90SGVhdG1hcHMgPC0gZnVuY3Rpb24oZGxpc3QsIGZvbnRzaXplKXsKICAKICAjIEluaXRpYWxpemUgdmFyaWFibGVzCiAgaT0xCiAgcGxpc3QgPC0gdmVjdG9yKCJsaXN0IiwgNCkKICBuYW1lcyhwbGlzdCkgPC0gbmFtZXMoZGxpc3QpCiAgCiAgIyBMb29wIHRocm91Z2ggZGlzdGFuY2VzCiAgZm9yKGQgaW4gZGxpc3QpewogICAgcGxpc3RbW2ldXSA8LSBwaGVhdG1hcChhcy5tYXRyaXgoZCksIAogICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cyA9IGQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlcmluZ19kaXN0YW5jZV9jb2xzID0gZCwKICAgICAgICAgICAgICAgICAgICAgICAgICBmb250c2l6ZSA9IGZvbnRzaXplLAogICAgICAgICAgICAgICAgICAgICAgICAgIGZvbnRzaXplX2NvbCA9IGZvbnRzaXplLTUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgZm9udHNpemVfcm93ID0gZm9udHNpemUtNSwKICAgICAgICAgICAgICAgICAgICAgICAgICBhbm5vdGF0aW9uX2NvbCA9IG1hdGNvbCwKICAgICAgICAgICAgICAgICAgICAgICAgICBhbm5vdGF0aW9uX3JvdyA9IG1hdGNvbCwKICAgICAgICAgICAgICAgICAgICAgICAgICBhbm5vdGF0aW9uX2NvbG9ycyA9IGxpc3QoaG9zdF9kaXNlYXNlID0gYygnSGVhbHRoeScgPSAnYmx1ZScsICdJQlMnID0gJ3JlZCcpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyX3Jvd3MgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfY29scyA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlcmluZ19tZXRob2QgPSAnY29tcGxldGUnLCAjIGhjIG1ldGhvZAogICAgICAgICAgICAgICAgICAgICAgICAgIG1haW4gPSBuYW1lcyhkbGlzdClbaV0pICMgaGF2ZSBuYW1lIG9mIGRpc3RhbmNlIGFzIHRpdGxlCiAgICBpIDwtIGkrMQogIH0KICAKICByZXR1cm4ocGxpc3QpCn0KCgojIEdldCB0aGUgaGVhdG1hcHMKaGVhdG1wLmFncCA8LSBwbG90SGVhdG1hcHMoZGxpc3QgPSBkaXN0LmFncCwgZm9udHNpemUgPSA4KQpgYGAKCgo=